package model

import (
	"errors"
	"fmt"
	"hertz/demo/biz/util"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func AddSample(DB *gorm.DB, sample *Sample) error {
	return DB.Create(sample).Error
}

const (
	SAMPLE_STAGE_PENDING = "pending"
	SAMPLE_STAGE_WORKING = "working"
	SAMPLE_STAGE_CONFIRM = "confirm"
)

func IsValidSampleStage(stage string) bool {
	switch stage {
	case SAMPLE_STAGE_PENDING:
		return true
	case SAMPLE_STAGE_WORKING:
		return true
	case SAMPLE_STAGE_CONFIRM:
		return true
	default:
		return false
	}
}

func ListSample(DB *gorm.DB, stage string) ([]Sample, error) {
	ret := []Sample{}

	if stage == "" {
		if err := DB.Find(&ret).Error; err != nil {
			return nil, err
		}
		return ret, nil
	}

	if stage == SAMPLE_STAGE_PENDING {
		subQuery := DB.Table("selections").Distinct("sample_id")

		if err := DB.Where("id NOT IN (?)", subQuery).Find(&ret).Error; err != nil {
			return nil, err
		}
		return ret, nil
	}

	if stage == SAMPLE_STAGE_WORKING {
		subQuery := DB.Table("selections").Distinct("sample_id")
		subQuery2 := DB.Table("selections").Distinct("sample_id").Where("status = ?", SELECTION_CONFIRM)

		if err := DB.Where("id IN (?)", subQuery).Where("id NOT IN (?)", subQuery2).Find(&ret).Error; err != nil {
			return nil, err
		}
		return ret, nil
	}

	if stage == SAMPLE_STAGE_CONFIRM {
		subQuery := DB.Table("selections").Distinct("sample_id").Where("status = ?", SELECTION_CONFIRM)

		if err := DB.Where("id IN (?)", subQuery).Find(&ret).Error; err != nil {
			return nil, err
		}
		return ret, nil
	}

	return nil, util.CanRetError(fmt.Errorf("invalid stage: %s", stage))
}

func FindSample(DB *gorm.DB, id string) (*Sample, error) {
	var ret Sample
	ret.ID = id
	err := DB.Take(&ret).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		} else {
			return nil, err
		}
	}
	return &ret, nil
}

type Conflict struct {
	ID       string `json:"id"`
	Path     string `json:"path"`
	HashInDB string `json:"hash_in_db"`
	NewHash  string `json:"new_hash"`
}

func SyncSample(DB *gorm.DB, samples []Sample) ([]Conflict, error) {
	type Entry struct {
		ID           string `json:"id"`
		Path         string `json:"path"`
		DICOMDIRHash string `json:"dicomdir_hash"`
	}

	hashTable := map[string]string{}
	idSet := []string{}
	for i := 0; i < len(samples); i++ {
		id := samples[i].ID
		hashTable[id] = samples[i].DICOMDIRHash
		idSet = append(idSet, id)

		samples[i].Selections = nil
	}

	entries := []Entry{}
	err := DB.Transaction(func(tx *gorm.DB) error {
		err := tx.Table("samples").Select("id", "path", "dicomdir_hash").Where("id IN ?", idSet).Scan(&entries).Error
		if err != nil {
			return err
		}

		return tx.Clauses(clause.Insert{
			Modifier: "OR IGNORE",
		}).Create(&samples).Error
	})

	// check conflict
	ret := []Conflict{}
	for _, entry := range entries {
		newHash := hashTable[entry.ID]
		if newHash != entry.DICOMDIRHash {
			ret = append(ret, Conflict{
				ID:       entry.ID,
				Path:     entry.Path,
				HashInDB: entry.DICOMDIRHash,
				NewHash:  newHash,
			})
		}
	}

	if err != nil {
		return nil, err
	}

	return ret, nil
}

func GetSampleByID(db *gorm.DB, id string) (*Sample, error) {
	var ret Sample
	ret.ID = id
	err := db.Preload("Selections").Take(&ret).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &ret, nil
}
